<?php
/**
 * Snoopy类
 * Frame/Library/Org/Net/Snoopy.class.php
 *
 * @author Snoopy
 * @copyright Snoopy
 * @version Snoopy
 * @注释 mcyami
 */
namespace Org\Net;
class Snoopy {
	/**** Public variables ****/
	
	/* user definable vars */
	
	public $scheme = 'http'; // 协议,http or https
	public $host = "www.php.net"; // 主机地址
	public $port = 80; // 端口
	public $proxy_host = ""; // 代理主机地址
	public $proxy_port = ""; // 代理端口
	public $proxy_user = ""; // 代理用户名
	public $proxy_pass = ""; // 代理密码
	

	public $agent = "Snoopy v2.0.0"; // 伪装的UserAgent
	public $referer = ""; // 来源信息
	public $cookies = array (); // cookie,$cookies["username"]="joe";
	public $rawheaders = array (); // 原始头,$rawheaders["Content-type"]="text/html";
	

	public $maxredirs = 5; // 最大重定向次数. 0 = disallow,0为不允许重定向
	public $lastredirectaddr = ""; // 在最后重定向地址中包含地址
	public $offsiteok = true; // 允许重定向到站外
	public $maxframes = 0; // 最大框架层数. 0 = disallow
	public $expandlinks = true; // 是否将链接都补全为完整地址 this only applies to fetchlinks()  submitlinks(), and submittext()
	

	public $passcookies = true; // pass set cookies back through redirects. NOTE: this currently does not respect dates, domains or paths.
	

	public $user = ""; // 用户认证的用户名
	public $pass = ""; // 用户认证的密码
	

	// http accept types HTTP允许的类型
	public $accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*";
	
	public $results = ""; // 输出的内容
	

	public $error = ""; // 错误信息
	public $response_code = ""; // 服务器返回代码
	public $headers = array (); // headers returned from server sent here
	public $maxlength = 500000; // max return data length (body)
	public $read_timeout = 0; // timeout on read operations, in seconds
	// supported only since PHP 4 Beta 4
	// set to 0 to disallow timeouts
	public $timed_out = false; // if a read operation timed out
	public $status = 0; // http request status
	

	public $temp_dir = "/tmp"; // temporary directory that the webserver
	// has permission to write to.
	// under Windows, this should be C:\temp
	

	public $curl_path = false;
	// deprecated, snoopy no longer uses curl for https requests,
	// but instead requires the openssl extension.
	

	// send Accept-encoding: gzip?
	public $use_gzip = true;
	
	// file or directory with CA certificates to verify remote host with
	public $cafile;
	public $capath;
	
	/**** 私有属性 ****/
	
	private $_maxlinelen = 4096; // max line length (headers)
	

	private $_httpmethod = "GET"; // default http request method
	private $_httpversion = "HTTP/1.0"; // default http request version
	private $_submit_method = "POST"; // default submit method
	private $_submit_type = "application/x-www-form-urlencoded"; // default submit type
	private $_mime_boundary = ""; // MIME boundary for multipart/form-data submit type
	private $_redirectaddr = false; // will be set if page fetched is a redirect
	private $_redirectdepth = 0; // increments on an http redirect
	private $_frameurls = array (); // frame src urls
	private $_framedepth = 0; // increments on frame depth
	

	private $_isproxy = false; // set if using a proxy server
	private $_fp_timeout = 30; // timeout for socket connection
	

	/**
	 * 从指定网页中抓取数据
	 * 如果你正在抓取的是一个框架，Snoopy将会将每个框架追踪后存入数组中，然后存入 $this->results。
	 * @param string $URI 指定目标地址
	 * @return $this->results
	 */
	public function fetch($URI) {
		$URI_PARTS = parse_url ( $URI );
		if (! empty ( $URI_PARTS ["user"] )) {
			$this->user = $URI_PARTS ["user"];
		}
		if (! empty ( $URI_PARTS ["pass"] )) {
			$this->pass = $URI_PARTS ["pass"];
		}
		if (empty ( $URI_PARTS ["query"] )) {
			$URI_PARTS ["query"] = '';
		}
		if (empty ( $URI_PARTS ["path"] )) {
			$URI_PARTS ["path"] = '';
		}
		
		$fp = null;
		
		switch (strtolower ( $URI_PARTS ["scheme"] )) {
			case "https" :
				if (! extension_loaded ( 'openssl' )) {
					trigger_error ( "openssl extension required for HTTPS", E_USER_ERROR );
					exit ();
				}
				$this->port = 443;
			case "http" :
				$this->scheme = strtolower ( $URI_PARTS ["scheme"] );
				$this->host = $URI_PARTS ["host"];
				if (! empty ( $URI_PARTS ["port"] )) {
					$this->port = $URI_PARTS ["port"];
				}
				if ($this->_connect ( $fp )) {
					if ($this->_isproxy) {
						// using proxy, send entire URI
						$this->_httprequest ( $URI, $fp, $URI, $this->_httpmethod );
					} else {
						$path = $URI_PARTS ["path"] . ($URI_PARTS ["query"] ? "?" . $URI_PARTS ["query"] : "");
						// no proxy, send only the path
						$this->_httprequest ( $path, $fp, $URI, $this->_httpmethod );
					}
					
					$this->_disconnect ( $fp );
					
					if ($this->_redirectaddr) {
						/* url was redirected, check if we've hit the max depth */
						if ($this->maxredirs > $this->_redirectdepth) {
							// only follow redirect if it's on this site, or offsiteok is true
							if (preg_match ( "|^https?://" . preg_quote ( $this->host ) . "|i", $this->_redirectaddr ) || $this->offsiteok) {
								/* follow the redirect */
								$this->_redirectdepth ++;
								$this->lastredirectaddr = $this->_redirectaddr;
								$this->fetch ( $this->_redirectaddr );
							}
						}
					}
					
					if ($this->_framedepth < $this->maxframes && count ( $this->_frameurls ) > 0) {
						$frameurls = $this->_frameurls;
						$this->_frameurls = array ();
						
						while ( ! ! list ( , $frameurl ) = each ( $frameurls ) ) {
							if ($this->_framedepth < $this->maxframes) {
								$this->fetch ( $frameurl );
								$this->_framedepth ++;
							} else {
								break;
							}
						}
					}
				} else {
					return false;
				}
				return $this;
				break;
			default :
				// not a valid protocol
				$this->error = 'Invalid protocol "' . $URI_PARTS ["scheme"] . '"\n';
				return false;
				break;
		}
		return $this;
	}
	
	/**
	 * http(s) 方式提交表单
	 * @access public
	 * @param string $URI post提交地址
	 * @param array $formvars 表单变量,格式:$formvars["var"] = "val";
	 * @param array $formfiles 提交的文件,格式:$formfiles["var"] = "/dir/filename.ext";
	 * @return $this->results
	 */
	public function submit($URI, $formvars = "", $formfiles = "") {
		//unset ( $postdata );
		$postdata = $this->_prepare_post_body ( $formvars, $formfiles );
		$URI_PARTS = parse_url ( $URI );
		if (! empty ( $URI_PARTS ["user"] )) {
			$this->user = $URI_PARTS ["user"];
		}
		if (! empty ( $URI_PARTS ["pass"] )) {
			$this->pass = $URI_PARTS ["pass"];
		}
		if (empty ( $URI_PARTS ["query"] )) {
			$URI_PARTS ["query"] = '';
		}
		if (empty ( $URI_PARTS ["path"] )) {
			$URI_PARTS ["path"] = '';
		}
		switch (strtolower ( $URI_PARTS ["scheme"] )) {
			case "https" :
				if (! extension_loaded ( 'openssl' )) {
					trigger_error ( "openssl extension required for HTTPS", E_USER_ERROR );
					exit ();
				}
				$this->port = 443;
			case "http" :
				$this->scheme = strtolower ( $URI_PARTS ["scheme"] );
				$this->host = $URI_PARTS ["host"];
				if (! empty ( $URI_PARTS ["port"] )) {
					$this->port = $URI_PARTS ["port"];
				}
				if ($this->_connect ( $fp )) {
					if ($this->_isproxy) {
						// using proxy, send entire URI
						$this->_httprequest ( $URI, $fp, $URI, $this->_submit_method, $this->_submit_type, $postdata );
					} else {
						$path = $URI_PARTS ["path"] . ($URI_PARTS ["query"] ? "?" . $URI_PARTS ["query"] : "");
						// no proxy, send only the path
						$this->_httprequest ( $path, $fp, $URI, $this->_submit_method, $this->_submit_type, $postdata );
					}
					
					$this->_disconnect ( $fp );
					
					if ($this->_redirectaddr) {
						/* url was redirected, check if we've hit the max depth */
						if ($this->maxredirs > $this->_redirectdepth) {
							if (! preg_match ( "|^" . $URI_PARTS ["scheme"] . "://|", $this->_redirectaddr )) {
								$this->_redirectaddr = $this->_expandlinks ( $this->_redirectaddr, $URI_PARTS ["scheme"] . "://" . $URI_PARTS ["host"] );
							}
							// only follow redirect if it's on this site, or offsiteok is true
							if (preg_match ( "|^https?://" . preg_quote ( $this->host ) . "|i", $this->_redirectaddr ) || $this->offsiteok) {
								/* follow the redirect */
								$this->_redirectdepth ++;
								$this->lastredirectaddr = $this->_redirectaddr;
								if (strpos ( $this->_redirectaddr, "?" ) > 0) {
									$this->fetch ( $this->_redirectaddr ); // the redirect has changed the request method from post to get
								} else {
									$this->submit ( $this->_redirectaddr, $formvars, $formfiles );
								}
							}
						}
					}
					
					if ($this->_framedepth < $this->maxframes && count ( $this->_frameurls ) > 0) {
						$frameurls = $this->_frameurls;
						$this->_frameurls = array ();
						while ( ! ! list ( , $frameurl ) = each ( $frameurls ) ) {
							if ($this->_framedepth < $this->maxframes) {
								$this->fetch ( $frameurl );
								$this->_framedepth ++;
							} else {
								break;
							}
						}
					}
				} else {
					return false;
				}
				return $this;
				break;
			default :
				// not a valid protocol
				$this->error = 'Invalid protocol "' . $URI_PARTS ["scheme"] . '"\n';
				return false;
				break;
		}
		return $this;
	}
	
	/**
	 * 从网页中抓取链接地址
	 * @access public
	 * @param string $URI 要抓取的网页地址
	 * @return $this->results 抓取的内容
	 */
	public function fetchlinks($URI) {
		if ($this->fetch ( $URI ) !== false) {
			if ($this->lastredirectaddr) {
				$URI = $this->lastredirectaddr;
			}
			if (is_array ( $this->results )) {
				for($x = 0; $x < count ( $this->results ); $x ++) {
					$this->results [$x] = $this->_striplinks ( $this->results [$x] );
				}
			} else {
				$this->results = $this->_striplinks ( $this->results );
			}
			if ($this->expandlinks) {
				$this->results = $this->_expandlinks ( $this->results, $URI );
			}
			return $this;
		} else {
			return false;
		}
	}
	
	/**
	 * 从网页中抓取表单元素
	 * @access public
	 * @param string $URI 要抓取的地址
	 * @return $this->results 抓取的内容
	 */
	public function fetchform($URI) {
		if ($this->fetch ( $URI ) !== false) {
			if (is_array ( $this->results )) {
				for($x = 0; $x < count ( $this->results ); $x ++) {
					$this->results [$x] = $this->_stripform ( $this->results [$x] );
				}
			} else {
				$this->results = $this->_stripform ( $this->results );
			}
			return $this;
		} else {
			return false;
		}
	}
	
	/**
	 * 从网页中抓取文字内容
	 * @access public
	 * @param string $URI 要抓取的地址
	 * @return $this->results 抓取的内容
	 */
	public function fetchtext($URI) {
		if ($this->fetch ( $URI ) !== false) {
			if (is_array ( $this->results )) {
				for($x = 0; $x < count ( $this->results ); $x ++) {
					$this->results [$x] = $this->_striptext ( $this->results [$x] );
				}
			} else {
				$this->results = $this->_striptext ( $this->results );
			}
			return $this;
		} else {
			return false;
		}
	}
	
	/**
	 * 提交表单,并抓取返回内容中的链接(grab links from a form submission)
	 * @access public
	 * @param string $URI 表单提交地址
	 * @param array $formvars 表单变量数组
	 * @param array $formfiles 表单文件数组
	 */
	public function submitlinks($URI, $formvars = "", $formfiles = "") {
		if ($this->submit ( $URI, $formvars, $formfiles ) !== false) {
			if ($this->lastredirectaddr) {
				$URI = $this->lastredirectaddr;
			}
			if (is_array ( $this->results )) {
				for($x = 0; $x < count ( $this->results ); $x ++) {
					$this->results [$x] = $this->_striplinks ( $this->results [$x] );
					if ($this->expandlinks) {
						$this->results [$x] = $this->_expandlinks ( $this->results [$x], $URI );
					}
				}
			} else {
				$this->results = $this->_striplinks ( $this->results );
				if ($this->expandlinks) {
					$this->results = $this->_expandlinks ( $this->results, $URI );
				}
			}
			return $this;
		} else {
			return false;
		}
	}
	
	/**
	 * 提交表单并抓取返回内容中的文本文字(grab text from a form submission)
	 * @access public
	 * @param string $URI 表单提交地址
	 * @param array $formvars 表单变量数组
	 * @param array $formfiles 表单文件数组
	 */
	public function submittext($URI, $formvars = "", $formfiles = "") {
		if ($this->submit ( $URI, $formvars, $formfiles ) !== false) {
			if ($this->lastredirectaddr) {
				$URI = $this->lastredirectaddr;
			}
			if (is_array ( $this->results )) {
				for($x = 0; $x < count ( $this->results ); $x ++) {
					$this->results [$x] = $this->_striptext ( $this->results [$x] );
					if ($this->expandlinks) {
						$this->results [$x] = $this->_expandlinks ( $this->results [$x], $URI );
					}
				}
			} else {
				$this->results = $this->_striptext ( $this->results );
				if ($this->expandlinks) {
					$this->results = $this->_expandlinks ( $this->results, $URI );
				}
			}
			return $this;
		} else {
			return false;
		}
	}
	
	/**
	 * 设置表单提交内容类型为"multipart/form-data"(文件上传时使用此格式)(Set the form submission content type to multipart/form-data)
	 * @access public
	 */
	public function set_submit_multipart() {
		$this->_submit_type = "multipart/form-data";
		return $this;
	}
	
	/**
	 * 设置表单提交内容类型为"application/x-www-form-urlencoded" (Set the form submission content type to application/x-www-form-urlencoded)
	 * @access public
	 */
	public function set_submit_normal() {
		$this->_submit_type = "application/x-www-form-urlencoded";
		return $this;
	}
	
	/*======================================================================*\
        Private functions
    \*======================================================================*/
	
	/**
	 * 过滤html文档中的超链接,并返回
	 * @access private
	 * @param string $document
	 * @return array 超链接数组
	 */
	private function _striplinks($document) {
		// 正则中的注释方法
		preg_match_all ( "'<\s*a\s.*?href\s*=\s*			# 匹配a标签 <a href=
						([\"\'])?					# 匹配单引号或双引号
						(?(1) (.*?)\\1 | ([^\s\>]+))		# 如果引号存在,匹配下一个相对应的引号,否则匹配下一个空白
						'isx", $document, $links );
		// catenate the non-empty matches from the conditional subpattern
		while ( ! ! list ( $key, $val ) = each ( $links [2] ) ) {
			if (! empty ( $val )) {
				$match [] = $val;
			}
		}
		while ( ! ! list ( $key, $val ) = each ( $links [3] ) ) {
			if (! empty ( $val )) {
				$match [] = $val;
			}
		}
		return $match;
	}
	
	/**
	 * 过滤html文档中的表单元素并返回
	 * @access private
	 * @param string $document html文档
	 * @return array $match 表单元素数组
	 */
	private function _stripform($document) {
		preg_match_all ( "'<\/?(FORM|INPUT|SELECT|TEXTAREA|(OPTION))[^<>]*>(?(2)(.*(?=<\/?(option|select)[^<>]*>[\r\n]*)|(?=[\r\n]*))|(?=[\r\n]*))'Usi", $document, $elements );
		
		// catenate the matches
		$match = implode ( "\r\n", $elements [0] );
		return $match;
	}
	
	/**
	 * 过滤html中的文本并返回
	 * @access private
	 * @param string $document
	 * @return $text
	 */
	private function _striptext($document) {
		$search = array (
			"'<script[^>]*?>.*?</script>'si", // strip out javascript
			"'<[\/\!]*?[^<>]*?>'si", // strip out html tags
			"'([\r\n])[\s]+'", // strip out white space
			"'&(quot|#34|#034|#x22);'i", // replace html entities
			"'&(amp|#38|#038|#x26);'i", // added hexadecimal values
			"'&(lt|#60|#060|#x3c);'i", "'&(gt|#62|#062|#x3e);'i", "'&(nbsp|#160|#xa0);'i", "'&(iexcl|#161);'i", "'&(cent|#162);'i", "'&(pound|#163);'i", "'&(copy|#169);'i", "'&(reg|#174);'i", "'&(deg|#176);'i", "'&(#39|#039|#x27);'", "'&(euro|#8364);'i", // europe
			"'&a(uml|UML);'", // german
			"'&o(uml|UML);'", "'&u(uml|UML);'", "'&A(uml|UML);'", "'&O(uml|UML);'", "'&U(uml|UML);'", "'&szlig;'i" 
		);
		$replace = array ("", "", "\\1", "\"", "&", "<", ">", " ", chr ( 161 ), chr ( 162 ), chr ( 163 ), chr ( 169 ), chr ( 174 ), chr ( 176 ), chr ( 39 ), chr ( 128 ), "ä", "ö", "ü", "Ä", "Ö", "Ü", "ß" );
		
		$text = preg_replace ( $search, $replace, $document );
		return $text;
	}
	
	/**
	 * 补全链接为完整的URL
	 * @access private
	 * @param string $links 链接
	 * @param string $URI 完整的URL地址
	 * @return $expandedLinks
	 */
	private function _expandlinks($links, $URI) {
		preg_match ( "/^[^\?]+/", $URI, $match );
		$match = preg_replace ( "|/[^\/\.]+\.[^\/\.]+$|", "", $match [0] );
		$match = preg_replace ( "|/$|", "", $match );
		$match_part = parse_url ( $match );
		$match_root = $match_part ["scheme"] . "://" . $match_part ["host"];
		$search = array ("|^http://" . preg_quote ( $this->host ) . "|i", "|^(\/)|i", "|^(?!http://)(?!mailto:)|i", "|/\./|", "|/[^\/]+/\.\./|" );
		$replace = array ("", $match_root . "/", $match . "/", "/", "/" );
		$expandedLinks = preg_replace ( $search, $replace, $links );
		return $expandedLinks;
	}
	
	/**
	 * 从服务器获取http(s)数据
	 * @param string $url 目标地址
	 * @param string $fp 当前打开的文件指针
	 * @param string $URI 完整的链接地址
	 * @param string $http_method 获取方法
	 * @param string $content_type 文档类型
	 * @param mixed $body 发送的内容,如果有的话
	 */
	private function _httprequest($url, $fp, $URI, $http_method, $content_type = "", $body = "") {
		$cookie_headers = '';
		if ($this->passcookies && $this->_redirectaddr) {
			$this->setcookies ();
		}
		$URI_PARTS = parse_url ( $URI );
		if (empty ( $url )) {
			$url = "/";
		}
		// 请求方法
		$headers = $http_method . " " . $url . " " . $this->_httpversion . "\r\n";
		// 主机地址及端口
		if (! empty ( $this->host ) && ! isset ( $this->rawheaders ['Host'] )) {
			$headers .= "Host: " . $this->host;
			if (! empty ( $this->port ) && $this->port != '80') {
				$headers .= ":" . $this->port;
			}
			$headers .= "\r\n";
		}
		// 用户浏览器
		if (! empty ( $this->agent )) {
			$headers .= "User-Agent: " . $this->agent . "\r\n";
		}
		if (! empty ( $this->accept )) {
			$headers .= "Accept: " . $this->accept . "\r\n";
		}
		if ($this->use_gzip) {
			// make sure PHP was built with --with-zlib
			// and we can handle gzipp'ed data
			if (function_exists ( 'gzinflate' )) {
				$headers .= "Accept-encoding: gzip\r\n";
			} else {
				trigger_error ( "use_gzip is on, but PHP was built without zlib support." . "  Requesting file(s) without gzip encoding.", E_USER_NOTICE );
			}
		}
		// 来源
		if (! empty ( $this->referer )) {
			$headers .= "Referer: " . $this->referer . "\r\n";
		}
		// cookie
		if (! empty ( $this->cookies )) {
			if (! is_array ( $this->cookies )) {
				$this->cookies = ( array ) $this->cookies;
			}
			reset ( $this->cookies );
			if (count ( $this->cookies ) > 0) {
				$cookie_headers .= 'Cookie: ';
				foreach ( $this->cookies as $cookieKey => $cookieVal ) {
					$cookie_headers .= $cookieKey . "=" . urlencode ( $cookieVal ) . "; ";
				}
				$headers .= substr ( $cookie_headers, 0, - 2 ) . "\r\n";
			}
		}
		// 其他头信息
		if (! empty ( $this->rawheaders )) {
			if (! is_array ( $this->rawheaders )) {
				$this->rawheaders = ( array ) $this->rawheaders;
			}
			while ( ! ! list ( $headerKey, $headerVal ) = each ( $this->rawheaders ) ) {
				$headers .= $headerKey . ": " . $headerVal . "\r\n";
			}
		}
		if (! empty ( $content_type )) {
			$headers .= "Content-type: $content_type";
			if ($content_type == "multipart/form-data") {
				$headers .= "; boundary=" . $this->_mime_boundary;
			}
			$headers .= "\r\n";
		}
		if (! empty ( $body )) {
			$headers .= "Content-length: " . strlen ( $body ) . "\r\n";
		}
		if (! empty ( $this->user ) || ! empty ( $this->pass )) {
			$headers .= "Authorization: Basic " . base64_encode ( $this->user . ":" . $this->pass ) . "\r\n";
		}
		//add proxy auth headers
		if (! empty ( $this->proxy_user )) {
			$headers .= 'Proxy-Authorization: ' . 'Basic ' . base64_encode ( $this->proxy_user . ':' . $this->proxy_pass ) . "\r\n";
		}
		$headers .= "\r\n";
		// set the read timeout if needed
		if ($this->read_timeout > 0) {
			socket_set_timeout ( $fp, $this->read_timeout );
		}
		$this->timed_out = false;
		fwrite ( $fp, $headers . $body, strlen ( $headers . $body ) );
		$this->_redirectaddr = false;
		unset ( $this->headers );
		// content was returned gzip encoded?
		$is_gzipped = false;
		while ( ! ! $currentHeader = fgets ( $fp, $this->_maxlinelen ) ) {
			if ($this->read_timeout > 0 && $this->_check_timeout ( $fp )) {
				$this->status = - 100;
				return false;
			}
			if ($currentHeader == "\r\n") {
				break;
			}
			// if a header begins with Location: or URI:, set the redirect
			if (preg_match ( "/^(Location:|URI:)/i", $currentHeader )) {
				// get URL portion of the redirect
				preg_match ( "/^(Location:|URI:)[ ]+(.*)/i", chop ( $currentHeader ), $matches );
				// look for :// in the Location header to see if hostname is included
				if (! preg_match ( "|\:\/\/|", $matches [2] )) {
					// no host in the path, so prepend
					$this->_redirectaddr = $URI_PARTS ["scheme"] . "://" . $this->host . ":" . $this->port;
					// eliminate double slash
					if (! preg_match ( "|^/|", $matches [2] )) {
						$this->_redirectaddr .= "/" . $matches [2];
					} else {
						$this->_redirectaddr .= $matches [2];
					}
				} else {
					$this->_redirectaddr = $matches [2];
				}
			}
			if (preg_match ( "|^HTTP/|", $currentHeader )) {
				if (preg_match ( "|^HTTP/[^\s]*\s(.*?)\s|", $currentHeader, $status )) {
					$this->status = $status [1];
				}
				$this->response_code = $currentHeader;
			}
			if (preg_match ( "/Content-Encoding: gzip/", $currentHeader )) {
				$is_gzipped = true;
			}
			$this->headers [] = $currentHeader;
		}
		$results = '';
		do {
			$_data = fread ( $fp, $this->maxlength );
			if (strlen ( $_data ) == 0) {
				break;
			}
			$results .= $_data;
		} while ( true );
		
		// gunzip
		if ($is_gzipped) {
			// per http://www.php.net/manual/en/function.gzencode.php
			$results = substr ( $results, 10 );
			$results = gzinflate ( $results );
		}
		if ($this->read_timeout > 0 && $this->_check_timeout ( $fp )) {
			$this->status = - 100;
			return false;
		}
		// check if there is a a redirect meta tag
		if (preg_match ( "'<meta[\s]*http-equiv[^>]*?content[\s]*=[\s]*[\"\']?\d+;[\s]*URL[\s]*=[\s]*([^\"\']*?)[\"\']?>'i", $results, $match )) {
			$this->_redirectaddr = $this->_expandlinks ( $match [1], $URI );
		}
		// have we hit our frame depth and is there frame src to fetch?
		if (($this->_framedepth < $this->maxframes) && preg_match_all ( "'<frame\s+.*src[\s]*=[\'\"]?([^\'\"\>]+)'i", $results, $match )) {
			$this->results [] = $results;
			for($x = 0; $x < count ( $match [1] ); $x ++) {
				$this->_frameurls [] = $this->_expandlinks ( $match [1] [$x], $URI_PARTS ["scheme"] . "://" . $this->host );
			}
		} elseif (is_array ( $this->results )) {
			// have we already fetched framed content?
			$this->results [] = $results;
		} else {
			// no framed content
			$this->results = $results;
		}
		return $this;
	}
	
	/**
	 * 为重定向设置cookie
	 * @access public
	 */
	public function setcookies() {
		for($x = 0; $x < count ( $this->headers ); $x ++) {
			if (preg_match ( '/^set-cookie:[\s]+([^=]+)=([^;]+)/i', $this->headers [$x], $match )) {
				$this->cookies [$match [1]] = urldecode ( $match [2] );
			}
		}
		return $this;
	}
	
	/**
	 * 检测是否超时(checks whether timeout has occurred)
	 * @access private
	 * @param resource $fp 文件指针
	 */
	private function _check_timeout($fp) {
		if ($this->read_timeout > 0) {
			$fp_status = socket_get_status ( $fp );
			if ($fp_status ["timed_out"]) {
				$this->timed_out = true;
				return true;
			}
		}
		return false;
	}
	
	/**
	 * 构造一个socket连接
	 * @access private
	 * @param resource $fp 文件指针
	 */
	private function _connect(&$fp) {
		if (! empty ( $this->proxy_host ) && ! empty ( $this->proxy_port )) {
			$this->_isproxy = true;
			$host = $this->proxy_host;
			$port = $this->proxy_port;
			if ($this->scheme == 'https') {
				trigger_error ( "HTTPS connections over proxy are currently not supported", E_USER_ERROR );
				exit ();
			}
		} else {
			$host = $this->host;
			$port = $this->port;
		}
		$this->status = 0;
		$context_opts = array ();
		if ($this->scheme == 'https') {
			// if cafile or capath is specified, enable certificate
			// verification (including name checks)
			if (isset ( $this->cafile ) || isset ( $this->capath )) {
				$context_opts ['ssl'] = array ('verify_peer' => true, 'CN_match' => $this->host, 'disable_compression' => true );
				if (isset ( $this->cafile )) {
					$context_opts ['ssl'] ['cafile'] = $this->cafile;
				}
				if (isset ( $this->capath )) {
					$context_opts ['ssl'] ['capath'] = $this->capath;
				}
			}
			$host = 'ssl://' . $host;
		}
		$context = stream_context_create ( $context_opts );
		if (version_compare ( PHP_VERSION, '5.0.0', '>' )) {
			if ($this->scheme == 'http') {
				$host = "tcp://" . $host;
			}
			$fp = stream_socket_client ( "$host:$port", $errno, $errmsg, $this->_fp_timeout, STREAM_CLIENT_CONNECT, $context );
		} else {
			$fp = fsockopen ( $host, $port, $errno, $errstr, $this->_fp_timeout, $context );
		}
		if ($fp) {
			// socket connection succeeded
			return true;
		} else {
			// socket connection failed
			$this->status = $errno;
			switch ($errno) {
				case - 3 :
					$this->error = "socket creation failed (-3)";
				case - 4 :
					$this->error = "dns lookup failure (-4)";
				case - 5 :
					$this->error = "connection refused or timed out (-5)";
				default :
					$this->error = "connection failed (" . $errno . ")";
			}
			return false;
		}
	}
	
	/**
	 * 断开socket连接
	 * @access private
	 * @param resource $fp 文件指针
	 */
	private function _disconnect($fp) {
		return (fclose ( $fp ));
	}
	
	/**
	 * 根据编码类型来准备post主体(Prepare post body according to encoding type)
	 * @access private
	 * @param array $formvars 表单变量
	 * @param array $formfiles 表单文件
	 * @return post body
	 */
	private function _prepare_post_body($formvars, $formfiles) {
		settype ( $formvars, "array" );
		settype ( $formfiles, "array" );
		$postdata = '';
		if (count ( $formvars ) == 0 && count ( $formfiles ) == 0) {
			return;
		}
		switch ($this->_submit_type) {
			case "application/x-www-form-urlencoded" :
				reset ( $formvars );
				while ( ! ! list ( $key, $val ) = each ( $formvars ) ) {
					if (is_array ( $val ) || is_object ( $val )) {
						while ( ! ! list ( $cur_key, $cur_val ) = each ( $val ) ) {
							$postdata .= urlencode ( $key ) . "[]=" . urlencode ( $cur_val ) . "&";
						}
					} else
						$postdata .= urlencode ( $key ) . "=" . urlencode ( $val ) . "&";
				}
				break;
			
			case "multipart/form-data" :
				$this->_mime_boundary = "Snoopy" . md5 ( uniqid ( microtime () ) );
				reset ( $formvars );
				while ( ! ! list ( $key, $val ) = each ( $formvars ) ) {
					if (is_array ( $val ) || is_object ( $val )) {
						while ( ! ! list ( $cur_key, $cur_val ) = each ( $val ) ) {
							$postdata .= "--" . $this->_mime_boundary . "\r\n";
							$postdata .= "Content-Disposition: form-data; name=\"$key\[\]\"\r\n\r\n";
							$postdata .= "$cur_val\r\n";
						}
					} else {
						$postdata .= "--" . $this->_mime_boundary . "\r\n";
						$postdata .= "Content-Disposition: form-data; name=\"$key\"\r\n\r\n";
						$postdata .= "$val\r\n";
					}
				}
				reset ( $formfiles );
				while ( ! ! list ( $field_name, $file_names ) = each ( $formfiles ) ) {
					settype ( $file_names, "array" );
					while ( ! ! list ( , $file_name ) = each ( $file_names ) ) {
						if (! is_readable ( $file_name )) {
							continue;
						}
						$fp = fopen ( $file_name, "r" );
						$file_content = fread ( $fp, filesize ( $file_name ) );
						fclose ( $fp );
						$base_name = basename ( $file_name );
						$postdata .= "--" . $this->_mime_boundary . "\r\n";
						$postdata .= "Content-Disposition: form-data; name=\"$field_name\"; filename=\"$base_name\"\r\n\r\n";
						$postdata .= "$file_content\r\n";
					}
				}
				$postdata .= "--" . $this->_mime_boundary . "--\r\n";
				break;
		}
		return $postdata;
	}
	
	/**
	 * 返回一个请求的结果
	 * @access public
	 * @return string $this->results
	 */
	public function getResults() {
		return $this->results;
	}
}